package com.che.software.testato.web.controller;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import com.che.software.testato.business.IterationManager;
import com.che.software.testato.business.MatrixItemManager;
import com.che.software.testato.business.MatrixResultManager;
import com.che.software.testato.business.ProjectManager;
import com.che.software.testato.business.RandomIndexManager;
import com.che.software.testato.business.ReferencesManager;
import com.che.software.testato.business.VersionManager;
import com.che.software.testato.business.exception.IterationSearchManagerException;
import com.che.software.testato.business.exception.MatrixItemSearchManagerException;
import com.che.software.testato.business.exception.MatrixItemUpdateManagerException;
import com.che.software.testato.business.exception.MatrixResultSearchManagerException;
import com.che.software.testato.business.exception.RandomIndexSearchManagerException;
import com.che.software.testato.domain.entity.Iteration;
import com.che.software.testato.domain.entity.MatrixItem;
import com.che.software.testato.domain.entity.MatrixResult;
import com.che.software.testato.domain.entity.ReferenceTableTuple;
import com.che.software.testato.domain.entity.search.IterationSearch;
import com.che.software.testato.domain.entity.search.MatrixItemSearch;
import com.che.software.testato.domain.entity.search.MatrixResultSearch;
import com.che.software.testato.domain.entity.search.ProjectSearch;
import com.che.software.testato.domain.entity.search.RandomIndexSearch;
import com.che.software.testato.domain.entity.search.VersionSearch;
import com.che.software.testato.domain.enumeration.AssignmentStatus;
import com.che.software.testato.web.controller.acontroller.AbstractController;

/**
 * Controller used to manage the analytical prioritization.
 * 
 * @author Clement HELIOU (clement.heliou@che-software.com).
 * @copyright Che Software.
 * @license GNU General Public License.
 * @see AbstractController, Serializable.
 * @since July, 2011.
 * 
 * This file is part of Testato.
 * 
 * Testato is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 * 
 * Testato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with Testato. If not, see <http://www.gnu.org/licenses/>.
 * 
 * Testato's logo is a creation of Arrioch (http://arrioch.deviantart.com/) and it's distributed under the terms of the Creative Commons License.
 */
@Component("analyticalPrioritizationController")
@ManagedBean(name = "analyticalPrioritizationController")
@Scope("session")
@SessionScoped
public class AnalyticalPrioritizationController extends AbstractController implements Serializable {

	/**
	 * Constants.
	 */
	private static final Logger LOGGER = Logger.getLogger(AnalyticalPrioritizationController.class);
	private static final long serialVersionUID = -4170059451291480372L;

	/**
	 * Members.
	 */
	private Boolean comparisonMatrixComplete, iterationCompleted;
	private Double consistencyRation;
	private Integer scriptsContainedInMatrixSize;
	private Iteration currentPrioritization;
	@Autowired
	private transient IterationManager iterationManager;
	private List<MatrixItem> comparisonMatrix;
	private List<MatrixResult> comparisonMatrixResults;
	private List<String> scriptsNames;
	private MatrixItem currentEditedMatrixItem;
	@Autowired
	private transient MatrixItemManager matrixItemManager;
	@Autowired
	private transient MatrixResultManager matrixResultManager;
	@Autowired
	private transient ProjectManager projectManager;
	@Autowired
	private transient RandomIndexManager randomIndexManager;
	@Autowired
	private transient ReferencesManager referencesManager;
	@Autowired
	private transient VersionManager versionManager;

	/**
	 * Retrieves the reference table for the analytical prioritization with the AHP.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @return the resulting object.
	 * @since July, 2011.
	 */
	public List<ReferenceTableTuple> getReferenceTable() {
		LOGGER.debug("getReferenceTable().");
		return referencesManager.getReferenceTable();
	}

	/**
	 * Method called to redirect to analytical prioritization page.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @return the url to follow.
	 * @since July, 2011.
	 */
	public String onAnalyticalPrioritization() {
		LOGGER.debug("onAnalyticalPrioritization().");
		init();
		return "analyticalPrioritization.xhtml?faces-redirect=true";
	}

	/**
	 * Action launched on a cell update. Update an item of the comparison matrix and its reverse.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @return null because none redirection is necessary.
	 * @since August, 2011.
	 */
	public String onCellUpdate() {
		LOGGER.debug("onCellUpdate(" + currentEditedMatrixItem.getFirstScriptLabel() + "," + currentEditedMatrixItem.getSecondScriptLabel() + ").");
		try {
			matrixItemManager.updateMatrixItem(currentEditedMatrixItem);
			MatrixItem reverse = getReverseMatrixItem(currentEditedMatrixItem.getFirstScript(), currentEditedMatrixItem.getSecondScript());
			reverse.setValue(1 / currentEditedMatrixItem.getValue());
			matrixItemManager.updateMatrixItem(reverse);
			comparisonMatrix = null; // To trigger the data loading.
			comparisonMatrixComplete = null;
			iterationCompleted = null;
		}
		catch (MatrixItemUpdateManagerException e) {
			LOGGER.error("Error during the matrix item updating.", e);
		}
		return null;
	}

	/**
	 * Action launched on a matrix prioritization. Creates the results and update the page with them.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @return null because none redirection is necessary.
	 * @since August, 2011.
	 */
	public String onPrioritization() {
		LOGGER.debug("onPrioritization().");
		try {
			matrixResultManager.createMatrixResults(currentPrioritization.getIterationAssignmentId());
			currentPrioritization.setAnalyticalStringStatus(null);
			currentPrioritization.setAnalyticalStatus(AssignmentStatus.ENDED);
			iterationManager.updateIteration(currentPrioritization);
			matrixResultManager.createConsistencyIndex(currentPrioritization.getIterationAssignmentId());
			IterationSearch searchBean = new IterationSearch();
			searchBean.setIterationAssignmentId(currentPrioritization.getIterationAssignmentId());
			currentPrioritization = iterationManager.searchIteration(searchBean).get(0);
			currentPrioritization.setVersion(versionManager.searchVersion(new VersionSearch(currentPrioritization.getVersionId())).get(0));
			currentPrioritization.setProject(projectManager.searchProject(new ProjectSearch(currentPrioritization.getVersion().getProjectId())).get(0));
			comparisonMatrixResults = null; // To trigger the data loading.
			comparisonMatrixComplete = false;
			iterationCompleted = null;
		}
		catch (Exception e) {
			LOGGER.error("Error during the matrix results creation.", e);
		}
		return null;
	}

	/**
	 * Retrieves the reverse matrix item from a first and a second script. It's used when a matrix item is updated in the comparison matrix because its reverse must have the reverse value.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @param firstScript the first script id.
	 * @param secondScript the second script id.
	 * @return the resulting matrix item.
	 * @since August, 2011.
	 */
	private MatrixItem getReverseMatrixItem(int firstScript, int secondScript) {
		LOGGER.debug("getReverseMatrixItemId(" + firstScript + "," + secondScript + ").");
		for (MatrixItem matrixItem : getComparisonMatrix()) {
			if (matrixItem.getFirstScript() == secondScript && matrixItem.getSecondScript() == firstScript) {
				return matrixItem;
			}
		}
		return null; // Doesn't happened.
	}

	/**
	 * Method used to initialize the different fields to trigger the data loading.
	 * 
	 * @author Clement HELIOU (clement.heliou@che-software.com).
	 * @since August, 2011.
	 */
	private void init() {
		comparisonMatrix = null; // To trigger the data loading.
		comparisonMatrixComplete = null;
		iterationCompleted = null;
	}

	/**
	 * Getter for the private field value currentPrioritization.
	 * 
	 * @return the currentPrioritization field value.
	 */
	public Iteration getCurrentPrioritization() {
		return currentPrioritization;
	}

	/**
	 * Setting a value to the currentPrioritization field.
	 * 
	 * @param currentPrioritization the value to set.
	 */
	public void setCurrentPrioritization(Iteration currentPrioritization) {
		this.currentPrioritization = currentPrioritization;
	}

	/**
	 * Getter for the private field value referencesManager.
	 * 
	 * @return the referencesManager field value.
	 */
	public ReferencesManager getReferencesManager() {
		return referencesManager;
	}

	/**
	 * Setting a value to the referencesManager field.
	 * 
	 * @param referencesManager the value to set.
	 */
	public void setReferencesManager(ReferencesManager referencesManager) {
		this.referencesManager = referencesManager;
	}

	/**
	 * Getter for the private field value matrixItemManager.
	 * 
	 * @return the matrixItemManager field value.
	 */
	public MatrixItemManager getMatrixItemManager() {
		return matrixItemManager;
	}

	/**
	 * Setting a value to the matrixItemManager field.
	 * 
	 * @param matrixItemManager the value to set.
	 */
	public void setMatrixItemManager(MatrixItemManager matrixItemManager) {
		this.matrixItemManager = matrixItemManager;
	}

	/**
	 * Getter for the private field value comparisonMatrix.
	 * 
	 * @return the comparisonMatrix field value.
	 */
	public List<MatrixItem> getComparisonMatrix() {
		if (null == comparisonMatrix) {
			LOGGER.debug("getComparisonMatrix(): initialization.");
			try {
				comparisonMatrix = matrixItemManager.searchMatrixItem(new MatrixItemSearch(currentPrioritization.getIterationAssignmentId()));
			}
			catch (MatrixItemSearchManagerException e) {
				LOGGER.error("Error during the comparison matrix recovery.", e);
			}
		}
		return comparisonMatrix;
	}

	/**
	 * Setting a value to the comparisonMatrix field.
	 * 
	 * @param comparisonMatrix the value to set.
	 */
	public void setComparisonMatrix(List<MatrixItem> comparisonMatrix) {
		this.comparisonMatrix = comparisonMatrix;
	}

	/**
	 * Getter for the private field value scriptsContainedInMatrixSize.
	 * 
	 * @return the scriptsContainedInMatrixSize field value.
	 */
	public Integer getScriptsContainedInMatrixSize() {
		if (null == scriptsContainedInMatrixSize) {
			scriptsContainedInMatrixSize = Integer.valueOf((int) Math.sqrt(getComparisonMatrix().size()));
		}
		return scriptsContainedInMatrixSize;
	}

	/**
	 * Setting a value to the scriptsContainedInMatrixSize field.
	 * 
	 * @param scriptsContainedInMatrixSize the value to set.
	 */
	public void setScriptsContainedInMatrixSize(Integer scriptsContainedInMatrixSize) {
		this.scriptsContainedInMatrixSize = scriptsContainedInMatrixSize;
	}

	/**
	 * Getter for the private field value scriptsNames.
	 * 
	 * @return the scriptsNames field value.
	 */
	public List<String> getScriptsNames() {
		if (null == scriptsNames) {
			LOGGER.debug("getScriptsNames(): intialization.");
			scriptsNames = new ArrayList<String>();
			for (int i = 0; i < getScriptsContainedInMatrixSize(); i++) {
				scriptsNames.add(getComparisonMatrix().get(i).getSecondScriptLabel());
			}
		}
		return scriptsNames;
	}

	/**
	 * Setting a value to the scriptsNames field.
	 * 
	 * @param scriptsNames the value to set.
	 */
	public void setScriptsNames(List<String> scriptsNames) {
		this.scriptsNames = scriptsNames;
	}

	/**
	 * Getter for the private field value currentEditedMatrixItem.
	 * 
	 * @return the currentEditedMatrixItem field value.
	 */
	public MatrixItem getCurrentEditedMatrixItem() {
		return currentEditedMatrixItem;
	}

	/**
	 * Setting a value to the currentEditedMatrixItem field.
	 * 
	 * @param currentEditedMatrixItem the value to set.
	 */
	public void setCurrentEditedMatrixItem(MatrixItem currentEditedMatrixItem) {
		this.currentEditedMatrixItem = currentEditedMatrixItem;
	}

	/**
	 * Getter for the private field value iterationManager.
	 * 
	 * @return the iterationManager field value.
	 */
	public IterationManager getIterationManager() {
		return iterationManager;
	}

	/**
	 * Setting a value to the iterationManager field.
	 * 
	 * @param iterationManager the value to set.
	 */
	public void setIterationManager(IterationManager iterationManager) {
		this.iterationManager = iterationManager;
	}

	/**
	 * Getter for the private field value matrixResultManager.
	 * 
	 * @return the matrixResultManager field value.
	 */
	public MatrixResultManager getMatrixResultManager() {
		return matrixResultManager;
	}

	/**
	 * Setting a value to the matrixResultManager field.
	 * 
	 * @param matrixResultManager the value to set.
	 */
	public void setMatrixResultManager(MatrixResultManager matrixResultManager) {
		this.matrixResultManager = matrixResultManager;
	}

	/**
	 * Getter for the private field value comparisonMatrixResults.
	 * 
	 * @return the comparisonMatrixResults field value.
	 */
	public List<MatrixResult> getComparisonMatrixResults() {
		if (null == comparisonMatrixResults) {
			LOGGER.debug("getComparisonMatrixResults(): initialization.");
			try {
				comparisonMatrixResults = matrixResultManager.searchMatrixResult(new MatrixResultSearch(currentPrioritization.getIterationAssignmentId()));
			}
			catch (MatrixResultSearchManagerException e) {
				LOGGER.error("Error during the matrix results recovery.", e);
			}
		}
		return comparisonMatrixResults;
	}

	/**
	 * Setting a value to the comparisonMatrixResults field.
	 * 
	 * @param comparisonMatrixResults the value to set.
	 */
	public void setComparisonMatrixResults(List<MatrixResult> comparisonMatrixResults) {
		this.comparisonMatrixResults = comparisonMatrixResults;
	}

	/**
	 * Getter for the private field value comparisonMatrixComplete.
	 * 
	 * @return the comparisonMatrixComplete field value.
	 */
	public Boolean getComparisonMatrixComplete() {
		if (null == comparisonMatrixComplete) {
			LOGGER.debug("getComparisonMatrixComplete(): initialization.");
			try {
				comparisonMatrixComplete = matrixItemManager.isComparisonMatrixComplete(currentPrioritization.getIterationAssignmentId());
			}
			catch (MatrixItemSearchManagerException e) {
				LOGGER.error("Error during the comparison matrix completeness checking.", e);
			}
		}
		return comparisonMatrixComplete;
	}

	/**
	 * Setting a value to the comparisonMatrixComplete field.
	 * 
	 * @param comparisonMatrixComplete the value to set.
	 */
	public void setComparisonMatrixComplete(Boolean comparisonMatrixComplete) {
		this.comparisonMatrixComplete = comparisonMatrixComplete;
	}

	/**
	 * Getter for the private field value iterationCompleted.
	 * 
	 * @return the iterationCompleted field value.
	 */
	public Boolean getIterationCompleted() {
		if (null == iterationCompleted) {
			LOGGER.debug("getIterationCompleted(): initialization.");
			try {
				iterationCompleted = iterationManager.isIterationAssignmentCompleted(currentPrioritization.getIterationAssignmentId());
			}
			catch (IterationSearchManagerException e) {
				LOGGER.error("Error during the completeness iteration checking.", e);
			}
		}
		return iterationCompleted;
	}

	/**
	 * Setting a value to the iterationCompleted field.
	 * 
	 * @param iterationCompleted the value to set.
	 */
	public void setIterationCompleted(Boolean iterationCompleted) {
		this.iterationCompleted = iterationCompleted;
	}

	/**
	 * Getter for the private field value projectManager.
	 * 
	 * @return the projectManager field value.
	 */
	public ProjectManager getProjectManager() {
		return projectManager;
	}

	/**
	 * Setting a value to the projectManager field.
	 * 
	 * @param projectManager the value to set.
	 */
	public void setProjectManager(ProjectManager projectManager) {
		this.projectManager = projectManager;
	}

	/**
	 * Getter for the private field value versionManager.
	 * 
	 * @return the versionManager field value.
	 */
	public VersionManager getVersionManager() {
		return versionManager;
	}

	/**
	 * Setting a value to the versionManager field.
	 * 
	 * @param versionManager the value to set.
	 */
	public void setVersionManager(VersionManager versionManager) {
		this.versionManager = versionManager;
	}

	/**
	 * Getter for the private field value consistencyRation.
	 * 
	 * @return the consistencyRation field value.
	 */
	public Double getConsistencyRation() {
		if (null == consistencyRation) {
			LOGGER.debug("getConsistencyRation(): initialization.");
			try {
				Double randomIndex = randomIndexManager.searchRandomIndex(new RandomIndexSearch(scriptsContainedInMatrixSize)).get(0).getValue();
				consistencyRation = ((randomIndex != 0) ? currentPrioritization.getConsistencyIndex() / randomIndex : 0);
			}
			catch (RandomIndexSearchManagerException e) {
				LOGGER.error("Error during the consistency ration recovery.", e);
			}
		}
		return consistencyRation;
	}

	/**
	 * Setting a value to the consistencyRation field.
	 * 
	 * @param consistencyRation the value to set.
	 */
	public void setConsistencyRation(Double consistencyRation) {
		this.consistencyRation = consistencyRation;
	}

	/**
	 * Getter for the private field value randomIndexManager.
	 * 
	 * @return the randomIndexManager field value.
	 */
	public RandomIndexManager getRandomIndexManager() {
		return randomIndexManager;
	}

	/**
	 * Setting a value to the randomIndexManager field.
	 * 
	 * @param randomIndexManager the value to set.
	 */
	public void setRandomIndexManager(RandomIndexManager randomIndexManager) {
		this.randomIndexManager = randomIndexManager;
	}
}